package hamaster.jpbc.mgr;

import static hamaster.jpbc.mgr.Messages.getString;
import static hamaster.jpbc.userconfig.ConfigKeys.AES_PREFIX;
import static hamaster.jpbc.userconfig.ConfigKeys.RC4_PREFIX;
import static hamaster.jpbc.userconfig.ConfigKeys.SYMM_KEY_LENGTH;
import static hamaster.jpbc.userconfig.ConfigKeys.OPERATION_MODE;
import hamaster.jpbc.Main;
import hamaster.jpbc.crypt.Crypt;
import hamaster.jpbc.crypt.CryptIO;
import hamaster.jpbc.crypt.aes.AES;
import hamaster.jpbc.crypt.aes.OperationMode;
import hamaster.jpbc.crypt.aes.Rijndael;
import hamaster.jpbc.crypt.impl.SimpleCryptIOImpl;
import hamaster.jpbc.crypt.rc4.RC4;
import hamaster.jpbc.exception.CryptException;
import hamaster.jpbc.gui.callback.AsynchronousCallback;
import hamaster.jpbc.pbc.CipherText;
import hamaster.jpbc.pbc.PBC;
import hamaster.jpbc.pbc.PBCDriver;
import hamaster.jpbc.pbc.PlainText;
import hamaster.jpbc.util.Hash;
import hamaster.jpbc.util.SymmetricKeyGenerator;
import it.unisa.dia.gas.jpbc.Element;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Properties;

import javax.swing.JOptionPane;

public class SymmetricEncryptManager implements Runnable {
	public final static String SYMMETRIC_KEY_PREFIX = "sym"; //$NON-NLS-1$
	public final static String PLAIN_FILE_LENGTH = "plain.size"; //$NON-NLS-1$
	public final static String PLAIN_FILE_NAME = "plain.name"; //$NON-NLS-1$
	public final static String SYMMETRIC_KEY = "sym.method"; //$NON-NLS-1$
	public final static String SIGNATURE_PREFIX = "sign"; //$NON-NLS-1$
	public final static String SIGNED_MARK = "signed"; //$NON-NLS-1$
	public final static String PERSONAL_PARAMETER_PREFIX = "id"; //$NON-NLS-1$
	public final static String CIPHER_NAME_KEY = "cipher.name"; //$NON-NLS-1$
	public final static String OPERATION_MODE_KEY = "cipher.mode"; //$NON-NLS-1$

	protected AsynchronousCallback callback;
	protected File plain;
	protected String alice;
	protected String crypto;
	protected File cipher;
	protected File ibcID;
	protected boolean sign;
	protected boolean complete;
	protected boolean error;

	/**
	 * 创建一个SymmetricEncryptManager实例
	 * @param callback 回调函数
	 * @param crypto 加密方式
	 * @param plain 明文文件路径
	 * @param alice 接收者
	 * @param cipher 密文保存路径
	 */
	public SymmetricEncryptManager(AsynchronousCallback callback, String crypto, File plain,
			String alice, File cipher) {
		this.callback = callback;
		this.crypto = crypto;
		this.plain = plain;
		this.alice = alice;
		this.cipher = cipher;
		sign = false;
		complete = false;
		error = false;
	}

	@Override
	public void run() {
		long plainLength = plain.length();
		CryptIO io = new SimpleCryptIOImpl(plainLength, true);
		Crypt crypt = null;
		byte[] iv = null;
		int keyLength;
		if ("AES".equalsIgnoreCase(crypto)) { //$NON-NLS-1$
			crypt = new AES();
			iv = SymmetricKeyGenerator.nextKey(Rijndael.AES_BLOCK_SIZE);
			((AES) crypt).setInitializationVector(iv);
			OperationMode mode = Enum.valueOf(OperationMode.class, Main.getUserConfig().getProperty(AES_PREFIX + OPERATION_MODE));
			((AES) crypt).setOperationMode(mode);
			try {
				keyLength = Integer.parseInt(Main.getUserConfig().getProperty(AES_PREFIX + SYMM_KEY_LENGTH)) / 8;
			} catch (NumberFormatException e) {
				keyLength = 32;
			}
			crypt.setBlockSize(Main.ioBlockSize());
		} else if("RC4".equalsIgnoreCase(crypto)) { //$NON-NLS-1$
			crypt = new RC4();
			try {
				keyLength = Integer.parseInt(Main.getUserConfig().getProperty(RC4_PREFIX + SYMM_KEY_LENGTH)) / 8;
			} catch (NumberFormatException e) {
				keyLength = 32;
			}
		} else {
			callback.message(getString("SymmetricEncryptManager.unsupportedMethod"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
			return;
		}
		io.setBlockSize(Main.ioBlockSize());
		// 使用PBC加密密钥
		byte[] key = SymmetricKeyGenerator.nextKey(keyLength);
		PBC pbc = PBC.instance();
		Element gt = pbc.getPairing().getGT().newElement();
		byte[] keyBuffer = new byte[Main.IBC_BLOCK_SIZE];
		Arrays.fill(keyBuffer, (byte) 0);

		// 低SYMMETRIC_KEY_LENGTH位字节密钥
		System.arraycopy(key, 0, keyBuffer, Main.IBC_BLOCK_SIZE - keyLength, keyLength);
		// 高SYMMETRIC_KEY_LENGTH + AES_BLOCK_SIZE字节IV
		if (iv != null)
			System.arraycopy(iv, 0, keyBuffer, Main.IBC_BLOCK_SIZE - keyLength - Rijndael.AES_BLOCK_SIZE, Rijndael.AES_BLOCK_SIZE);

		gt.setFromBytes(keyBuffer);
		PlainText message = new PlainText();
		message.setElement(gt);
		message.setLength(keyLength);
		Element user = pbc.getPairing().getZr().newElement();
		user.setFromBytes(Hash.sha1(alice));
		CipherText ibcCipher = pbc.encrypt(message, user, Main.publicParameter());
		Properties metadata = ibcCipher.toProperties(SYMMETRIC_KEY_PREFIX);
		metadata.setProperty(SYMMETRIC_KEY, PBCDriver.hex(crypto.getBytes()));
		metadata.setProperty(PLAIN_FILE_LENGTH, plainLength + new String());
		if (iv != null) {
			OperationMode mode = Enum.valueOf(OperationMode.class, Main.getUserConfig().getProperty(AES_PREFIX + OPERATION_MODE));
			metadata.setProperty(OPERATION_MODE_KEY, PBCDriver.hex(mode.toString().getBytes()));
		}
		String cipherName = cipher.getName().substring(0, cipher.getName().length() - 4) + ".data";
		try {
			metadata.setProperty(PLAIN_FILE_NAME, PBCDriver.hex(plain.getName().getBytes("UTF-8")));
			metadata.setProperty(CIPHER_NAME_KEY, PBCDriver.hex(cipherName.getBytes("UTF-8")));
		} catch (UnsupportedEncodingException e) {
			metadata.setProperty(PLAIN_FILE_NAME, PBCDriver.hex(plain.getName().getBytes()));
			metadata.setProperty(CIPHER_NAME_KEY, PBCDriver.hex(cipherName.getBytes()));
		}
		// 数字签名
		Thread signd = new Thread(new SignatureThread(this, plain, cipher.getParentFile(), ibcID, metadata, cipher.getName(), sign));
		signd.start();
		crypt.setKey(key);
		io.setCrypt(crypt);
		io.setProgressListener(callback);
		try {
			File cipherData = new File(cipher.getParentFile(), cipherName);
			io.encrypt(plain, new FileOutputStream(cipherData));
		} catch (FileNotFoundException e) {
			callback.message(getString("SymmetricEncryptManager.cannotCreateCIpher"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
			return;
		} catch (IOException e) {
			callback.message(getString("SymmetricEncryptManager.cannotWriteCipher"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
			return;
		} catch (CryptException e) {
			callback.message(getString("SymmetricEncryptManager.errorEncrypting"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
			return;
		}
		callback.message(getString("SymmetricEncryptManager.waitingForSign"), AsynchronousCallback.TITLE_MESSAGE); //$NON-NLS-1$
		for (;;) {
			if (complete)
				break;
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
			}
		}
		callback.message("IBC", AsynchronousCallback.TITLE_MESSAGE); //$NON-NLS-1$
		if (error) {
			callback.message(getString("SymmetricEncryptManager.errorSaveMeta"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
		} else {
			callback.message(getString("SymmetricEncryptManager.finished"), JOptionPane.INFORMATION_MESSAGE); //$NON-NLS-1$
		}
	}

	public void setIbcID(File ibcID) {
		sign = true;
		this.ibcID = ibcID;
	}

	public void setComplete(boolean complete) {
		this.complete = complete;
	}

	public void setError(boolean error) {
		this.error = error;
	}
}
